<?php

// Copyright 2019 Hackware SpA <human@hackware.cl>
// "Hackware Web Services Core" is released under the MIT License terms.

namespace Hawese\Core\Exceptions;

use Exception;
use Symfony\Component\Debug\Exception\FlattenException;
use Illuminate\Validation\ValidationException;
use Illuminate\Http\Response;

class JsonExceptionRenderer
{
    private $exception;

    public function __construct(Exception $exception)
    {
        $this->exception = $exception;
    }

    /**
     * Best HTTP status based on available info.
     */
    private function statusCode(): int
    {
        if (method_exists($this->exception, 'getStatusCode')) {
            return $this->exception->getStatusCode();
        } elseif (property_exists($this->exception, 'status')) {
            return $this->exception->status;
        }

        return 500;
    }

    /**
     * Define message, if not set use HTTP status code
     */
    private function message(): string
    {
        if (empty($this->exception->getMessage())) {
            return Response::$statusTexts[$this->statusCode()];
        }

        return $this->exception->getMessage();
    }

    /**
     * Application defined errors use codes
     */
    private function errorCode()
    {
        if ($this->exception->getCode() === 0) {
            return null;
        }

        return $this->exception->getCode();
    }

    /**
     * Application defined errors contain details
     */
    private function detail(): ?array
    {
        if (method_exists($this->exception, 'getDetail')) {
            return $this->exception->getDetail();
        } elseif ($this->exception instanceof ValidationException) {
            return ['attributes' => $this->exception->errors()];
        }

        return null;
    }

    /**
     * Add debugging info if appropriate
     */
    private function debugInfo(): ?array
    {
        if (env('APP_DEBUG', config('app.debug', false))) {
            $fex = FlattenException::create($this->exception);

            return [
                'exception' => $fex->getClass(),
                'source' => $fex->getFile() . ':' . $fex->getLine(),
                'trace' => $fex->getTrace()
            ];
        }

        return null;
    }

    /**
     * Compact everything in an object.
     */
    public function build(): array
    {
        $object = [
            'code' => $this->errorCode(),
            'message' => $this->message(),
            'detail' => $this->detail(),
            'debug' => $this->debugInfo()
        ];

        return array_filter(
            $object,
            function ($item) {
                return isset($item);
            }
        );
    }

    /**
     * Return a full response
     */
    public function response()
    {
        return response()->json(
            ['error' => $this->build()],
            $this->statusCode()
        );
    }

    /**
     * Make response accessible to the outside world
     */
    public static function render(Exception $exception)
    {
        return (new self($exception))->response();
    }
}
